package ar.com.muebleria.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;


public class Reflection {

	private String className = null;
	private Class<?> mainClass = null;
	private Object instancia = null;
	
	/**
	 * Obtengo el objeto instanciado
	 * @return El objeto instanciado
	 */
	public Object getInstancia(){
		return this.instancia;
	}
	
	/**
	 * Constructor de la clase con el nombre de la clase que se 
	 * quiere inicializar.
	 * @param parClassName El nombre de la clase que se va a inicializar
	 * @throws LRSReflectionException En caso de que no se pueda ejecutar
	 */
	protected Reflection(String parClassName)
	throws Exception{
		this.setClassName(parClassName);
	}
	
	/**
	 * Constructor de la clase sin parametros.
	 * No inicializa ni la clase ni el objeto.
	 * @throws LRSReflectionException
	 */
	protected Reflection()
	throws Exception{
		this(null);
	}	
	
	/**
	 * Este metodo setea el atributo de className, el nombre de la clase,
	 * y luego inicializa el objeto Clase para chequear de que exista, siempre
	 * y cuando el nombre de la clase no sea nulo.
	 * @param parClassName El nombre de la clase
	 * @throws LRSReflectionException En caso de que algun error ocurra
	 */
	protected void setClassName(String parClassName)
	throws Exception{
		
		//Seteo el nombre de la clase
		this.className = parClassName;
		
		//En caso de que la clase no sea nulo, voy a buscar la misma clase
		//para ternerla disponible en caso de tener que inicializar.
		if (this.className != null){
			//Busco la clase.
			//System.out.println(this.className+" ----------------------------------------");
			try{
				this.mainClass = Class.forName(this.className);
			}
			catch(ClassNotFoundException cnfe){
				throw new Exception("No se encuentra la clase. "+cnfe.getMessage());		
			}
		}
		
	}
	
	/**
	 * Este metodo llama al contructor de la clase sin ningun parametro.
	 * @throws LRSReflectionException En caso de que algun error en la inicializacion ocurra.
	 */
	protected void newInstance()
	throws Exception{
		this.newInstance(null);
	}
	
	/***
	 * Este metodo llama al constructor de la clase con una serie de parametros que
	 * son enviados en un array para llamar al constructor.
	 * Este metodo analiza los objetos que vienen como parametros y determina los tipos
	 * de los mismos.
	 * Parametros del tipo nativo (int, long, etc) no son contemplados por los objetos y 
	 * por lo tanto es necesario llamar al constructor con los tipos.
	 * @param parParametrosDelConstructor Los parametros para inicializar la clase
	 * @throws LRSReflectionException En caso de que algun error ocurra
	 */
	protected void newInstance(Object[] parParametrosDelConstructor)
	throws Exception{
		
		Class[] tiposDeParametrosDelConstructor = null;
		Object[] parametrosDelConstructor = null;
		
		//Si hay parametros, determino los mismos para poder llamar al contructor.
		if (parParametrosDelConstructor != null){
			tiposDeParametrosDelConstructor = new Class[parParametrosDelConstructor.length];
			parametrosDelConstructor = new Object[parParametrosDelConstructor.length];
			//Recorro los vectores y luevo separo lo que corresponde a cada uno.
			for (int i=0;i<parParametrosDelConstructor.length; ++i){
				tiposDeParametrosDelConstructor[i] = parParametrosDelConstructor[i].getClass();
				parametrosDelConstructor[i] = parParametrosDelConstructor[i];
			}
		}
		//Llamo al constructor con los tipos y los parametros.
		this.newInstance(tiposDeParametrosDelConstructor,parametrosDelConstructor);
	}
	
	/**
	 * Este metodo inicializa la clase en un objeto con un array de tipos de parametros y 
	 * otro array con las instancia de los objetos.
	 * @param tiposDeParametrosDelConstructor El array con el tipo de los parametros
	 * @param parametrosDelConstructor El array con las instancias
	 * @throws LRSReflectionException En caso que no se pueda inicializar
	 */
	protected void newInstance(Class[] tiposDeParametrosDelConstructor, Object[] parametrosDelConstructor)
	throws Exception{
		
		try{
			//Instancio un contructor con los valores.
			Constructor constructor = this.mainClass.getConstructor(tiposDeParametrosDelConstructor);
			
			//Instancio una clase, llamando al constructor.
			this.instancia = constructor.newInstance(parametrosDelConstructor);		
		}
		//Si el constructor no fue encontrado.
		catch(NoSuchMethodException nsme){
			throw new Exception("No se encuentra el Constructor. "+nsme.getMessage());		
		}	
		//Si no se pudo acceder al constructor.
		catch(IllegalAccessException iae){
			throw new Exception("No se pudo acceder al Constructor. "+iae.getMessage());		
		}	
		//Si no se pudo invocar el cont5ructor.
		catch(InvocationTargetException ite){
			throw new Exception("No se pudo invocar el Constructor. "+ite.getMessage());		
		}	
		//Si la clase no pudo ser instanciada.
		catch(InstantiationException ie){
			throw new Exception("No se pudo instanciar el Constructor. "+ie.getMessage());		
		}	
	}
	
	/**
	 * Este metodo invoca a un metodo estatico del la clase que ya ha sido 
	 * establecida con anterioridad.
	 * El metodo no recibe parametros para su ejecucion
	 * @param nombreDelMetodo El nombre del metodo
	 * @throws LRSReflectionException En caso de que algun error ocurra.
	 */
	protected void executeMethodStatic(String nombreDelMetodo)
	throws Exception{
		this.executeMethod(nombreDelMetodo,null);
	}	
	
	/**
	 * Este metodo invoca a un metodo estatico del la clase que ya ha sido 
	 * establecida con anterioridad.
	 * El metodo recibe parametros que son determinados previos a hacer la 
	 * ejecucion, pero los parametros no pueden ser nativos. En ese caso, se
	 * debera hacer la ejecucion a mano.
	 * @param nombreDelMetodo El nombre del metorod a ejecutar
	 * @param parParametros Los parametros para la ejecucion
	 * @throws LRSReflectionException En caso de algun error de ejecucion
	 */
	protected void executeMethodStatic(String nombreDelMetodo, Object[] parParametros)
	throws Exception{
		this.executeMethod(nombreDelMetodo,parParametros);
	}
	
	/**
	 * Este metodo invoca a un metodo estatico del la clase que ya ha sido 
	 * establecida con anterioridad.
	 * El objeto debe estar instanciado para poder ejecutarse.
	 * El metodo no recibe parametros y puede o no retornar un valor.
	 * @param nombreDelMetodo El nombre del metorod a ejecutar
	 * @return El objeto que retorna el metodo
	 * @throws LRSReflectionException En caso de algun error de ejecucion
	 */
	protected Object executeMethod(String nombreDelMetodo)
	throws Exception{
		//La instancia no debe ser nula
		if (this.instancia == null)
			throw new Exception("La instancia no puede ser nula");
		//Ejecuta el metodo.
		return this.executeMethod(nombreDelMetodo,null);
	}
	
	/**
	 * Este metodo invoca a un metodo estatico del la clase que ya ha sido 
	 * establecida con anterioridad.
	 * El objeto debe estar instanciado para poder ejecutarse.
	 * El metodo recibe parametros que son determinados previos a hacer la 
	 * ejecucion, pero los parametros no pueden ser nativos. En ese caso, se
	 * debera hacer la ejecucion a mano. 
	 * @param nombreDelMetodo El nombre del metorod a ejecutar
	 * @param parParametros Los parametros para ejecutar
	 * @return El objeto que retorna el metodo
	 * @throws LRSReflectionException En caso de algun error de ejecucion
	 */
	protected Object executeMethod(String nombreDelMetodo, Object[] parParametros)
	throws Exception{
		
		//El nombre del metodo no puede ser nulo
		if (nombreDelMetodo == null)
			throw new Exception("El nombre del metodo no puede ser nulo");
		//El nombre de la clase no puede ser nulo
		if (this.className == null)
			throw new Exception("El nombre de la Clase no puede ser nula");

		Class[] tiposDeParametros = null;
		Object[] parametros = null;
		
		//Si los parametros no son nulo, determino cuales son.
		if (parParametros != null){
			tiposDeParametros = new Class[parParametros.length];
			parametros = new Object[parParametros.length];
			//Recorro los parametros y obtendo la clase y los parametros.
			for (int i=0;i<parParametros.length; ++i){
				tiposDeParametros[i] = parParametros[i].getClass();
				parametros[i] = parParametros[i];
			}
		}
		//Ejecuto el metodo
		return this.executeMethod(nombreDelMetodo,tiposDeParametros,parametros);
	}

	/**
	 * Este metodo invoca a un metodo estatico del la clase que ya ha sido 
	 * establecida con anterioridad.
	 * Puese ejecutarse en base a una lista de tipo de parametros y 
	 * otra lista con las instancias de los mismos.
	 * @param nombreDelMetodo El metodo a ejecutar
	 * @param tiposDeParametros Los tipos de los parametros
	 * @param parametros Los parametros
	 * @return En caso que retorne algun objeto
	 * @throws LRSReflectionException En caso de algun error.
	 */
	protected Object executeMethod(String nombreDelMetodo, Class[] tiposDeParametros,  Object[] parametros)
	throws Exception{
		Object returned = null;
		try{
			//Instancio un contructor con los valores e invoco el metodo
			Method metodo = this.mainClass.getMethod(nombreDelMetodo,tiposDeParametros);
			returned = metodo.invoke(instancia,parametros);					
		}
		//Si el constructor no fue encontrado.
		catch(NoSuchMethodException nsme){
			throw new Exception("No se encuentra el Metodo. "+nsme.getMessage());		
		}	
		//Si no se pudo acceder al constructor.
		catch(IllegalAccessException iae){
			throw new Exception("No se pudo acceder al Metodo. "+iae.getMessage());		
		}	
		//Si no se pudo invocar el cont5ructor.
		catch(InvocationTargetException ite){
			throw new Exception("No se pudo invocar el Metodo. "+ite.getMessage());		
		}
		//Retorno el valor, si corresponde
		return returned;
	}
	
	
}

